/**
 * 对axios二次封装，主要处理请求和响应的拦截
 * 通过发布-订阅模式处理返回异常问题
 *
 * @author: huarui
 */

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig,  Method as AxiosMethod, AxiosHeaders } from 'axios';
import { HttpAxiosConfig, AxiosSendRequestParam } from '@/domain/axios';
import { getAuthParams } from '@/utils/storage';
import checkResponseError from '@/api/checkResponseError';
import pubsub, { AXIOS_RESPONSE_ERROR, AXIOS_LOADING_HIDE, AXIOS_LOADING_SHOW } from '@/utils/pubsub';

class HttpAxios {
    instance: AxiosInstance;
    // 请求超时时间
    timeout = 10000;
    config: HttpAxiosConfig;
    // 是否显示loading
    showLoading = true;
    // 是否在页面跳转前清除所有未完成的请求
    isClearBeforeChangeUrl = true;
    abortControllerMap = new Map<string, AbortController>();

    constructor(config: HttpAxiosConfig = {}) {
        const instance = axios.create(config);
        // 请求拦截
        instance.interceptors.request.use(
            (config: InternalAxiosRequestConfig) => this.onBeforeRequest(config),
            (config: InternalAxiosRequestConfig) => this.onRequestRejected(config)
        );
        // 响应拦截
        instance.interceptors.response.use(
			(response: any) => this.onResponseResolved(response),
			(error: any) => this.onResponseRejected(error)
		);
        this.instance = instance;
        this.config = config;

        checkResponseError(); // 订阅请求失败事件
    }

    /**
     * 发送请求
     * @param {*} param0 { method, url, params, headers, config }
     */
    sendRequst(param: AxiosSendRequestParam) {
		let { method, url, headers, params, config } = param;
        const _method = method.toLocaleUpperCase();
        if (_method != 'FORM' && _method != 'GET' && _method != 'POST') {
            throw new Error(`请求方式${method}不支持`);
        }
        if (config.showLoading || true) {
            pubsub.emit(AXIOS_LOADING_SHOW, '显示loading');
            // TODO 这里需要做loading
        }
        // 是否需要登陆等信息
        if (!config.noAuth) {
            headers.setContentType('application/json; charset=UTF-8');
            // 使用 set 方法逐个设置请求头
            let authParams = getAuthParams();
            Object.entries(authParams).forEach(([key, value]) => {
                headers.set(key, value);
            });
        }
        const requestConfig: AxiosRequestConfig = { headers };
        let requestPromise: Promise<AxiosResponse>;
        if (_method === 'GET') {
            requestPromise = this.instance.get(url, { params, ...requestConfig });
        }
        if (_method === 'FORM') {
            headers.setContentType('application/x-www-form-urlencoded; charset=UTF-8');
            const formData = new FormData();
            for (const key in params) {
                formData.append(key, params[key]);
            }
            requestPromise = this.instance.post(url, formData, requestConfig);
        } else {
            requestPromise = this.instance.post(url, params, requestConfig);
        }
        requestPromise.finally(() => {
            // 请求完成后移除loading
            pubsub.emit(AXIOS_LOADING_HIDE);
        });
    }

    /**
     * 取消单个请求
     * @param {*} url 请求地址
     */
    cancelRequest(url = '') {
        const controller = this.abortControllerMap.get(url);
        if (controller) {
            controller.abort();
        }
    }

    /**
     * 取消所有请求
     */
    clearRequest() {
        for (const [url, controller] of this.abortControllerMap) {
            this.cancelRequest(url);
        }
        this.abortControllerMap.clear();
    }

    /**
     * 请求前拦截
     * @param {*} config
     * @returns
     */
    onBeforeRequest = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
        const controller: AbortController = new AbortController();
        this.abortControllerMap.set(config.url || '', controller);
        const res: InternalAxiosRequestConfig = {
            ...config,
            signal: controller.signal, // 取消请求
            timeout: this.timeout, // 超时时间
            withCredentials: true, // 跨域请求时是否需要带上cookie
        };
        return res;
    };

    /**
     * 请求错误拦截
     * @param {*} error
     * @returns
     */
    onRequestRejected = (error: any): Promise<any> => {
        return Promise.reject(error);
    };

    /**
     * 响应成功拦截
     * @param {*} response
     * @returns
     */
    onResponseResolved = (response: AxiosResponse): any => {
        let data = response.data || {};
        // 判断返回成功码
        const { resp_code, datas, msg } = data;
        if (resp_code === 0) {
            return datas || '';
        }
        pubsub.emit(AXIOS_RESPONSE_ERROR, data);
        return Promise.reject(data);
    };

    /**
     * 请求失败拦截
     * @param {*} error
     */
    onResponseRejected = (error: any): Promise<any> => {
        if (/timeout/g.test(error)) {
            pubsub.emit(AXIOS_RESPONSE_ERROR, '请求超时，请稍后再试！！！');
        } else {
            const { response } = error;
            const { status, statusText, data } = response || {};
            let msg = data?.resp_msg || CODE_MESSAGE[status as keyof typeof CODE_MESSAGE] || statusText;
            msg && pubsub.emit(AXIOS_RESPONSE_ERROR, msg);
        }
        return Promise.reject(error);
    };
}

const httpAxios = new HttpAxios();

export default httpAxios;

export const CODE_MESSAGE = {
    200: '服务器成功返回请求的数据。',
    201: '新建或修改数据成功。',
    202: '一个请求已经进入后台排队（异步任务）。',
    204: '删除数据成功。',
    400: '信息校验失败',
    401: '用户没有权限（令牌、用户名、密码错误）。',
    403: '用户得到授权，但是访问被禁止',
    404: '请求的资源不存在',
    406: '请求的格式不可得。',
    410: '请求的资源被永久删除，且不会再得到的。',
    422: '当创建一个对象时，发生一个验证错误。',
    500: '服务器发生错误，请检查服务器。',
    502: '网关错误。',
    503: '服务不可用，服务器暂时过载或维护。',
    504: '网关超时。',
};
